package evm

import (
	"encoding/hex"
	"fmt"
	"strings"

	"github.com/golang/protobuf/proto"
	"github.com/spf13/cobra"
	"hundsun.com/hsl/hschain/cmd/cli/common"
	"hundsun.com/hsl/hschain/common/util"
	eabi "hundsun.com/hsl/hschain/contract/evm/abi"
	evmcomm "hundsun.com/hsl/hschain/contract/evm/ext/common"
	"hundsun.com/hsl/hschain/contract/evm/types"
	pbevm "hundsun.com/hsl/hschain/protos/evm"
	"hundsun.com/hsl/hschain/protos/ledger"
	pbtypes "hundsun.com/hsl/hschain/protos/types"
	service "hundsun.com/hsl/hschain/rpc/impl"
)

// Cmd 是Evm命令行入口
func Cmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "evm",
		Short: "EVM contracts operation",
		Args:  cobra.MinimumNArgs(1),
	}

	cmd.AddCommand(
		deployContractCmd(),
		callContractCmd(),
		queryContractCmd(),
		queryEstimateGasCmd(),
	)
	cmd.PersistentFlags().Int32("chainID", 0, "chain ID")

	return cmd
}

// 部署EVM合约
func deployContractCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "deploy",
		Short: "deploy the EVM contract",
		Run:   deployContract,
	}
	addDeployContractFlags(cmd)
	return cmd
}

func deployContract(cmd *cobra.Command, args []string) {
	code, _ := cmd.Flags().GetString("code")
	abi, _ := cmd.Flags().GetString("abi")
	param, _ := cmd.Flags().GetString("param")
	gasPrice, _ := cmd.Flags().GetUint32("gas_price")

	// 获取abi param
	abiStr := abi
	if strings.Contains(abi, ".abi") || util.FileExists(abi) { // 传入的是文件
		abiB, err := util.ReadFile(abi)
		if nil != err {
			fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
			return
		}
		abiStr = string(abiB)
	}

	inputParam, err := eabi.Encode(abiStr, "", param)
	if err != nil {
		fmt.Printf("GenContractCallParam fail methodName %s, error %v \n", "", err)
		return
	}

	// 获取code
	var codebin []byte
	if strings.Contains(code, ".bin") || util.FileExists(code) { // 传入的是文件
		codebin, err = util.ReadFile(code)
		if nil != err {
			fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
			return
		}
	} else {
		if len(code) > 0 {
			codebin = []byte(code)
		}
	}

	tx, err := common.BuildTxWithCmd(cmd, types.EVM, nil, genEvmContractAction(gasPrice, evmcomm.FromHex(string(codebin)), inputParam))
	if err != nil {
		fmt.Println("deployContract BuildTxWithCmd fail", err)
		return
	}

	rpcLaddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
	common.RPCCall(rpcLaddr, service.ReceiveTransaction, tx, func(result interface{}) {
		fmt.Printf("transaction '%s' sent successfully \n", hex.EncodeToString(tx.Hash()))
	})
}

func addDeployContractFlags(cmd *cobra.Command) {
	opts := []common.CmdOption{
		common.WithRequiredFlag(common.FlagFee),
		common.WithHiddenFlag(common.FlagPayload),
		common.WithHiddenFlag(common.FlagTo),
		common.WithHiddenFlag(common.FlagExecTy),
		common.WithHiddenFlag(common.FlagAmount),
		common.WithHiddenFlag(common.FlagKeyAsymmetricAlgorithm),
		common.WithHiddenFlag(common.FlagKeyHashFamily),
		common.WithHiddenFlag(common.FlagKeySymmetricAlgorithm),
	}

	common.AddTransactionPublicFlag(cmd, opts...)

	cmd.Flags().StringP("code", "", "", "evm contract code(hex) or code path")
	cmd.MarkFlagRequired("code")
	cmd.Flags().StringP("abi", "", "", "evm contract abi(json) or abi path")
	cmd.MarkFlagRequired("abi")
	cmd.Flags().StringP("param", "", "{}", "evm contract constructor json fmt param('{\"key\":\"value\"}')")
	cmd.Flags().Uint32P("gas_price", "g", 1, "evm contract gas price (optional)")
}

// 调用EVM合约
func callContractCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "call",
		Short: "call the EVM contract",
		Run:   callContract,
	}
	addCallContractFlags(cmd)
	return cmd
}

func callContract(cmd *cobra.Command, args []string) {
	abi, _ := cmd.Flags().GetString("abi")
	param, _ := cmd.Flags().GetString("param")
	methodName, _ := cmd.Flags().GetString("method_name")
	gasPrice, _ := cmd.Flags().GetUint32("gas_price")

	// 获取abi param
	abiStr := abi
	if strings.Contains(abi, ".abi") || util.FileExists(abi) { // 传入的是文件
		abiB, err := util.ReadFile(abi)
		if nil != err {
			fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
			return
		}
		abiStr = string(abiB)
	}

	inputParam, err := eabi.Encode(abiStr, methodName, param)
	if err != nil {
		fmt.Printf("GenContractCallParam fail methodName %s, error %v \n", methodName, err)
		return
	}

	tx, err := common.BuildTxWithCmd(cmd, types.EVM, nil, genEvmContractAction(gasPrice, nil, inputParam))
	if err != nil {
		fmt.Println("callContract BuildTxWithCmd fail", err)
		return
	}

	rpcLaddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
	common.RPCCall(rpcLaddr, service.ReceiveTransaction, tx, func(result interface{}) {
		fmt.Printf("transaction '%s' sent successfully \n", hex.EncodeToString(tx.Hash()))
	})
}

func addCallContractFlags(cmd *cobra.Command) {
	opts := []common.CmdOption{
		common.WithRequiredFlag(common.FlagTo),
		common.WithRequiredFlag(common.FlagFee),
		common.WithHiddenFlag(common.FlagPayload),
		common.WithHiddenFlag(common.FlagExecTy),
		common.WithHiddenFlag(common.FlagAmount),
		common.WithHiddenFlag(common.FlagKeyAsymmetricAlgorithm),
		common.WithHiddenFlag(common.FlagKeyHashFamily),
		common.WithHiddenFlag(common.FlagKeySymmetricAlgorithm),
	}

	common.AddTransactionPublicFlag(cmd, opts...)

	cmd.Flags().StringP("abi", "", "", "evm contract abi(json) or abi path")
	cmd.MarkFlagRequired("abi")
	cmd.Flags().StringP("param", "", "{}", "evm contract constructor json fmt param('{\"key\":\"value\"}')")
	cmd.Flags().StringP("method_name", "", "", "evm contract call method name")
	cmd.MarkFlagRequired("method_name")
	cmd.Flags().Uint32P("gas_price", "g", 1, "evm contract gas price (optional)")
}

// 查询EVM合约
func queryContractCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "query",
		Short: "query the EVM contract",
		Run:   queryContract,
	}
	addQueryContractFlags(cmd)
	return cmd
}

func queryContract(cmd *cobra.Command, args []string) {
	abi, _ := cmd.Flags().GetString("abi")
	param, _ := cmd.Flags().GetString("param")
	methodName, _ := cmd.Flags().GetString("method_name")
	gasPrice, _ := cmd.Flags().GetUint32("gas_price")

	// 获取abi param
	abiStr := abi
	if strings.Contains(abi, ".abi") || util.FileExists(abi) { // 传入的是文件
		abiB, err := util.ReadFile(abi)
		if nil != err {
			fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
			return
		}
		abiStr = string(abiB)
	}

	inputParam, err := eabi.Encode(abiStr, methodName, param)
	if err != nil {
		fmt.Printf("GenContractCallParam fail methodName %s, error %v \n", methodName, err)
		return
	}

	tx, err := common.BuildTxWithCmd(cmd, types.EVM, nil, genEvmContractAction(gasPrice, nil, inputParam))
	if err != nil {
		fmt.Println("queryContract BuildTxWithCmd fail", err)
		return
	}

	req, err := common.CreateQueryContractReq(types.EVM, types.QueryEVMContract, tx)
	if err != nil {
		fmt.Printf("create query contract request failed, err: %s", err.Error())
		return
	}

	rpcLaddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
	common.RPCCall(rpcLaddr, service.QueryContract, req, func(res interface{}) {
		obj := res.(service.JSONRPCResp)
		if len(obj.Payload) == 0 {
			fmt.Println("JSONRPCResp payload is NULL")
			return
		}
		val, err := util.Bytes2Struct(&ledger.TransactionReceipt{}, obj.Payload)
		if err != nil {
			fmt.Println("Bytes2Struct TransactionReceipt fail", err)
			return
		}
		recpt := val.(*ledger.TransactionReceipt)
		//clicom.PrintJSON(clicom.FromPbTxReceipt(recpt))
		if len(recpt.GetCallResult()) > 0 {
			res, err := eabi.Decode(abiStr, methodName, recpt.GetCallResult())
			if err != nil {
				fmt.Println("callResult Decode fail", err)
				return
			}
			for _, v := range res {
				fmt.Println("the call result is", v.Name, v.Value)
			}
		}
	})
}

func addQueryContractFlags(cmd *cobra.Command) {
	opts := []common.CmdOption{
		common.WithRequiredFlag(common.FlagTo),
		common.WithHiddenFlag(common.FlagPayload),
		common.WithHiddenFlag(common.FlagExecTy),
		common.WithHiddenFlag(common.FlagAmount),
		common.WithHiddenFlag(common.FlagFee),
		common.WithHiddenFlag(common.FlagKeyAsymmetricAlgorithm),
		common.WithHiddenFlag(common.FlagKeyHashFamily),
		common.WithHiddenFlag(common.FlagKeySymmetricAlgorithm),
	}

	common.AddTransactionPublicFlag(cmd, opts...)

	cmd.Flags().StringP("abi", "", "", "evm contract abi(json) or abi path")
	cmd.MarkFlagRequired("abi")
	cmd.Flags().StringP("param", "", "{}", "evm contract constructor json fmt param('{\"key\":\"value\"}')")
	cmd.Flags().StringP("method_name", "", "", "evm contract call method name")
	cmd.MarkFlagRequired("method_name")
	cmd.Flags().Uint32P("gas_price", "g", 1, "evm contract gas price (optional)")
}

func queryEstimateGasCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "estimate",
		Short: "estimate EVM contract gas",
		Run:   queryEstimateGas,
	}
	addQueryEstimateGasFlags(cmd)
	return cmd
}

func queryEstimateGas(cmd *cobra.Command, args []string) {
	code, _ := cmd.Flags().GetString("code")
	abi, _ := cmd.Flags().GetString("abi")
	param, _ := cmd.Flags().GetString("param")
	methodName, _ := cmd.Flags().GetString("method_name")

	// 获取abi param
	abiStr := abi
	if strings.Contains(abi, ".abi") || util.FileExists(abi) { // 传入的是文件
		abiB, err := util.ReadFile(abi)
		if nil != err {
			fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
			return
		}
		abiStr = string(abiB)
	}

	inputParam, err := eabi.Encode(abiStr, methodName, param)
	if err != nil {
		fmt.Printf("GenContractCallParam fail methodName %s, error %v \n", methodName, err)
		return
	}

	// 获取code
	var codebin []byte
	if len(code) > 0 {
		if strings.Contains(code, ".bin") || util.FileExists(code) { // 传入的是文件
			codebin, err = util.ReadFile(code)
			if nil != err {
				fmt.Println("Can't read abi info, Pls set correct abi path and provide abi file as", abi)
				return
			}
		} else {
			if len(code) > 0 {
				codebin = []byte(code)
			}
		}
	}

	tx, err := common.BuildTxWithCmd(cmd, types.EVM, nil, genEvmContractAction(1, evmcomm.FromHex(string(codebin)), inputParam))
	if err != nil {
		fmt.Println("queryEstimateGas BuildTxWithCmd fail", err)
		return
	}

	req, err := common.CreateQueryContractReq(types.EVM, types.QueryEstimateGas, tx)
	if err != nil {
		fmt.Printf("create query contract request failed, err: %s", err.Error())
		return
	}

	rpcLaddr, _ := cmd.Flags().GetString(common.FlagRPCLaddr)
	common.RPCCall(rpcLaddr, service.QueryContract, req, func(res interface{}) {
		obj := res.(service.JSONRPCResp)
		if len(obj.Payload) == 0 {
			fmt.Println("JSONRPCResp payload is NULL")
			return
		}
		val, err := util.Bytes2Struct(&pbtypes.Uint64{}, obj.Payload)
		if err != nil {
			fmt.Println("Bytes2Struct TransactionReceipt fail", err)
			return
		}
		usedGas := val.(*pbtypes.Uint64)
		if usedGas == nil {
			fmt.Println("queryEstimateGas fail usedGas is nil")
			return
		}
		fmt.Println("the call result is", usedGas.GetI())
	})
}

func addQueryEstimateGasFlags(cmd *cobra.Command) {
	opts := []common.CmdOption{
		common.WithHiddenFlag(common.FlagPayload),
		common.WithHiddenFlag(common.FlagExecTy),
		common.WithHiddenFlag(common.FlagAmount),
		common.WithHiddenFlag(common.FlagFee),
		common.WithHiddenFlag(common.FlagKeyAsymmetricAlgorithm),
		common.WithHiddenFlag(common.FlagKeyHashFamily),
		common.WithHiddenFlag(common.FlagKeySymmetricAlgorithm),
	}

	common.AddTransactionPublicFlag(cmd, opts...)

	cmd.Flags().StringP("code", "", "", "evm contract code(hex) or code path")
	cmd.Flags().StringP("abi", "", "", "evm contract abi(json) or abi path")
	cmd.MarkFlagRequired("abi")
	cmd.Flags().StringP("param", "", "{}", "evm contract constructor json fmt param('{\"key\":\"value\"}')")
	cmd.Flags().StringP("method_name", "", "", "evm contract call method name")
}

func genEvmContractAction(gasPrice uint32, code, param []byte) []byte {
	action := &pbevm.EVMContractAction{
		GasPrice: gasPrice,
		Code:     code,
		Param:    param,
	}
	value, err := proto.Marshal(action)
	if err != nil {
		panic(fmt.Sprintln("genEvmContractAction", err))
	}
	return value
}
